import sympy as sym
import numpy as np

def isAlways(expr):
    """
    checks if the conditions in cond are always mathematically true and 
    returns an array of logical values.
    isAlways checks if cond holds true for all possible values of the
    symbolic variables in cond including all assumptions on the variables. 
    If an element in cond is always true, then the corresponding element in
    tf is a logical 1 (true). 
    Otherwise, the corresponding element in tf is a logical 0 (false).

    Args:
        expr : symbolic expression or array

    Returns:
        the result of a and b(a & b).
    """
    # SymPy simplified the expr already
    if isinstance(expr, (list, np.ndarray)):
        narr = np.array(expr)
        exprs = np.reshape(narr, (narr.size,))
        for idx in range(exprs.size):
            exprs[idx] = isAlways(exprs[idx])
        return exprs.reshape(narr.shape)
    else:
        result = sym.simplify(expr)
        return result if result.is_Boolean else False


# Test case 1: Input is symbolic expressions
x = sym.symbols('x')
# expr = sym.Abs(x) >= 0
expr = sym.Ge(sym.Abs(x),0)
assert isAlways(expr) == True



expr = sym.Eq((sym.sin(x)**2+sym.cos(x)**2), 1)
print(expr)
assert isAlways(expr) == True

expr = sym.Eq(sym.Abs(x),x)
assert isAlways(expr) == False

expr = sym.Eq(sym.log(x), x)
assert isAlways(expr) == False

# Test case 2: Input is 2 dim symbolic expressions
x = sym.symbols('x')
arr = [
    [sym.Ne(x, x+1), sym.Ge(abs(x), 0) , sym.Eq(x, x)],
    [sym.Eq(x, x+1), sym.Eq(abs(x), 0) , sym.Eq(x, x)]
    ]
result = np.array([
    [True,True,True],
    [False,False,True]
])
assert np.array_equal(isAlways(arr), result)
